/*
 * D-Bus Java Implementation Copyright (c) 2005-2006 Matthew Johnson This
 * program is free software; you can redistribute it and/or modify it under the
 * terms of either the GNU Lesser General Public License Version 2 or the
 * Academic Free Licence Version 2.1. Full licence texts are included in the
 * COPYING file with this program.
 */
package org.freedesktop.dbus;

import static org.freedesktop.dbus.Gettext._;

import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;

import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.MessageProtocolVersionException;
import org.freedesktop.dbus.exceptions.MessageTypeException;

public class MessageReader {
  private byte[]      body   = null;
  private byte[]      buf    = null;
  private byte[]      header = null;
  private InputStream in;
  private int[]       len    = new int[4];
  private byte[]      tbuf   = null;

  public MessageReader(InputStream in) {
    this.in = new BufferedInputStream(in);
  }

  public void close() throws IOException {
    in.close();
  }

  public Message readMessage() throws IOException, DBusException {
    int rv;
    /* Read the 12 byte fixed header, retrying as neccessary */
    if (null == buf) {
      buf = new byte[12];
      len[0] = 0;
    }
    if (len[0] < 12) {
      try {
        rv = in.read(buf, len[0], 12 - len[0]);
      } catch (SocketTimeoutException STe) {
        return null;
      }
      if (-1 == rv) { throw new EOFException(_("Underlying transport returned EOF")); }
      len[0] += rv;
    }
    if (len[0] == 0) { return null; }
    if (len[0] < 12) { return null; }

    /* Parse the details from the header */
    byte endian = buf[0];
    byte type = buf[1];
    byte protover = buf[3];
    if (protover > Message.PROTOCOL) {
      buf = null;
      throw new MessageProtocolVersionException(MessageFormat.format(_("Protocol version {0} is unsupported"),
          new Object[] { protover }));
    }

    /* Read the length of the variable header */
    if (null == tbuf) {
      tbuf = new byte[4];
      len[1] = 0;
    }
    if (len[1] < 4) {
      try {
        rv = in.read(tbuf, len[1], 4 - len[1]);
      } catch (SocketTimeoutException STe) {
        return null;
      }
      if (-1 == rv) { throw new EOFException(_("Underlying transport returned EOF")); }
      len[1] += rv;
    }
    if (len[1] < 4) { return null; }

    /* Parse the variable header length */
    int headerlen = 0;
    if (null == header) {
      headerlen = (int) Message.demarshallint(tbuf, 0, endian, 4);
      if (0 != headerlen % 8) {
        headerlen += 8 - (headerlen % 8);
      }
    } else {
      headerlen = header.length - 8;
    }

    /* Read the variable header */
    if (null == header) {
      header = new byte[headerlen + 8];
      System.arraycopy(tbuf, 0, header, 0, 4);
      len[2] = 0;
    }
    if (len[2] < headerlen) {
      try {
        rv = in.read(header, 8 + len[2], headerlen - len[2]);
      } catch (SocketTimeoutException STe) {
        return null;
      }
      if (-1 == rv) { throw new EOFException(_("Underlying transport returned EOF")); }
      len[2] += rv;
    }
    if (len[2] < headerlen) { return null; }

    /* Read the body */
    int bodylen = 0;
    if (null == body) {
      bodylen = (int) Message.demarshallint(buf, 4, endian, 4);
    }
    if (null == body) {
      body = new byte[bodylen];
      len[3] = 0;
    }
    if (len[3] < body.length) {
      try {
        rv = in.read(body, len[3], body.length - len[3]);
      } catch (SocketTimeoutException STe) {
        return null;
      }
      if (-1 == rv) { throw new EOFException(_("Underlying transport returned EOF")); }
      len[3] += rv;
    }
    if (len[3] < body.length) { return null; }

    Message m;
    switch (type) {
      case Message.MessageType.METHOD_CALL:
        m = new MethodCall();
        break;
      case Message.MessageType.METHOD_RETURN:
        m = new MethodReturn();
        break;
      case Message.MessageType.SIGNAL:
        m = new DBusSignal();
        break;
      case Message.MessageType.ERROR:
        m = new Error();
        break;
      default:
        throw new MessageTypeException(MessageFormat.format(_("Message type {0} unsupported"), new Object[] { type }));
    }

    try {
      m.populate(buf, header, body);
    } catch (DBusException DBe) {
      buf = null;
      tbuf = null;
      body = null;
      header = null;
      throw DBe;
    } catch (RuntimeException Re) {
      buf = null;
      tbuf = null;
      body = null;
      header = null;
      throw Re;
    }

    buf = null;
    tbuf = null;
    body = null;
    header = null;
    return m;
  }
}
